

我们在LLVM模块中收集编译单元的所有函数和全局变量。为了方便IR的生成，我们将前面几节中的所有函数包装在代码生成器类中。要获得一个编译器，还需要定义要为其生成代码的目标体系结构，并添加生成代码的Pass。我们将在下一章中实现所有这些，就先从代码生成器开始。\par

\hspace*{\fill} \par %插入空行
\textbf{包装代码生成器}

IR模块是我们为编译单元生成的所有元素的大括号。在全局级别，我们遍历模块级别的声明，并创建全局变量，并为过程调用代码生成。tinylang中的全局变量会映射到llvm::GobalValue类的实例。这个映射保存在Globals中，并可用于过程代码的生成:\par

\begin{lstlisting}[caption={}]
void CGModule::run(ModuleDeclaration *Mod) {
	for (auto *Decl : Mod->getDecls()) {
		if (auto *Var =
				llvm::dyn_cast<VariableDeclaration>(Decl)) {
			llvm::GlobalVariable *V = new llvm::GlobalVariable(
				*M, convertType(Var->getType()),
				/*isConstant=*/false,
				llvm::GlobalValue::PrivateLinkage, nullptr,
				mangleName(Var));
			Globals[Var] = V;
		} else if (auto *Proc =
						llvm::dyn_cast<ProcedureDeclaration>(
		Decl)) {
			CGProcedure CGP(*this);
			CGP.run(Proc);
		}
	}
}
\end{lstlisting}

该模块还持有LLVMContext类，并缓存最常用的LLVM类型。后者需要初始化，例如：对于64位整数类型:\par

\begin{lstlisting}[caption={}]
Int64Ty = llvm::Type::getInt64Ty(getLLVMCtx());
\end{lstlisting}

CodeGenerator类初始化LLVM IR模块，并调用该模块的代码生成。最重要的是，这个类必须知道我们为哪个目标体系结构生成代码。这个信息在llvm::TargetMachine类中传递，在驱动程序中设置:\par

\begin{lstlisting}[caption={}]
void CodeGenerator::run(ModuleDeclaration *Mod, std::string
FileName) {
	llvm::Module *M = new llvm::Module(FileName, Ctx);
	M->setTargetTriple(TM->getTargetTriple().getTriple());
	M->setDataLayout(TM->createDataLayout());
	CGModule CGM(M);
	CGM.run(Mod);
}
\end{lstlisting}

为了方便使用，还为代码生成器引入了工厂方法:\par

\begin{lstlisting}[caption={}]
CodeGenerator *CodeGenerator::create(llvm::TargetMachine *TM) {
	return new CodeGenerator(TM);
}
\end{lstlisting}

CodeGenerator类提供了创建IR代码的接口，非常适合在编译器驱动程序中使用。在集成之前，需要支持实现对机器码的生成。\par

\hspace*{\fill} \par %插入空行
\textbf{初始化目标机器类}

现在，只缺少创建目标机器类。在目标机器上，我们定义了用于生成代码的CPU体系结构。对于每个CPU，还可以使用一些可用的特性来影响代码生成，例如：CPU体系结构家族中较新的CPU可以支持向量指令。通过特性，我们可以打开或关闭向量指令的使用。为了支持从命令行设置所有这些选项，LLVM提供了一些支持代码。在Driver类中，添加了以下include变量:\par

\begin{lstlisting}[caption={}]
#include "llvm/CodeGen/CommandFlags.h"
\end{lstlisting}

这个include变量为编译器驱动程序添加了常用的命令行选项。许多LLVM工具也使用这些命令行选项，好处是为用户提供了一个公共界面(只缺少指定目标三元组的选项)。由于这非常好用，所以我们添加了这个:\par

\begin{lstlisting}[caption={}]
static cl::opt<std::string>
	MTriple("mtriple",
		cl::desc("Override target triple for module"));
\end{lstlisting}

创建目标机器码:\par

\begin{itemize}
\item 为了显示错误消息，应用程序的名称必须传递给函数:
\begin{lstlisting}[caption={}]
llvm::TargetMachine *
createTargetMachine(const char *Argv0) {
\end{lstlisting}

\item 收集命令行提供的所有信息。以下是代码生成器的选项、CPU的名称、应该开启(或关闭)的可能特性以及目标的“三元组”:
\begin{lstlisting}[caption={}]
	llvm::Triple = llvm::Triple(
		!MTriple.empty()
			? llvm::Triple::normalize(MTriple)
			: llvm::sys::getDefaultTargetTriple());
			
	llvm::TargetOptions =
		codegen::InitTargetOptionsFromCodeGenFlags(Triple);
	std::string CPUStr = codegen::getCPUStr();
	std::string FeatureStr = codegen::getFeaturesStr();
\end{lstlisting}

\item 在目标注册表中查找目标。如果发生错误，则显示错误消息并退出。一个可能的错误是，用户指定的不支持的三元组:
\begin{lstlisting}[caption={}]
	std::string Error;
	const llvm::Target *Target =
		llvm::TargetRegistry::lookupTarget(
						codegen::getMArch(), Triple,
						Error);
	
	if (!Target) {
		llvm::WithColor::error(llvm::errs(), Argv0) <<
						 Error;
		return nullptr;
	}
\end{lstlisting}

\item 在Target类的帮助下，使用用户请求的所有已知选项配置目标机器:
\begin{lstlisting}[caption={}]
	llvm::TargetMachine *TM = Target->
		createTargetMachine(
			Triple.getTriple(), CPUStr, FeatureStr,
			TargetOptions,
			llvm::Optional<llvm::Reloc::Model>(
								codegen::getRelocModel()));
	return TM;
}
\end{lstlisting}

\end{itemize}

通过目标机器实例，可以生成针对我们选择的CPU架构的IR代码。缺少的是对程序集文本的转换或目标代码文件的生成。我们将在下一节中添加这种支持。\par

\hspace*{\fill} \par %插入空行
\textbf{生成汇编程序文本和目标代码}

在LLVM中，IR代码通过Pass运行。每一遍执行一个任务，例如：删除死代码。我们将在第8章，优化IR中学习更多关于Pass的内容。输出汇编程序代码或目标文件也可以实现为Pass。\par

需要llvm::legacy::PassManager类来保存Pass，以便将代码发送到文件。还希望能够输出LLVM IR代码，因此也需要一个Pass来做这个。最后，使用llvm:: ToolOutputFile类进行文件操作:\par

\begin{lstlisting}[caption={}]
#include "llvm/IR/IRPrintingPasses.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Support/ToolOutputFile.h"
\end{lstlisting}

输出LLVM IR的另一个命令行选项也是必需的:\par

\begin{lstlisting}[caption={}]
static cl::opt<bool>
	EmitLLVM("emit-llvm",
		cl::desc("Emit IR code instead of assembler"),
		cl::init(false));
\end{lstlisting}

新的emit()方法中的第一个任务是处理输出文件的名称。如果从stdin读取输入(用减号-表示)，则将结果输出到stdout。ToolOutputFile类知道如何处理特殊文件名，-:\par

\begin{lstlisting}[caption={}]
bool emit(StringRef Argv0, llvm::Module *M,
			llvm::TargetMachine *TM,
			StringRef InputFilename) {
	CodeGenFileType FileType = codegen::getFileType();
	std::string OutputFilename;
	if (InputFilename == "-") {
		OutputFilename = "-";
	}
\end{lstlisting}

否则，根据用户给出的命令行选项，我们将删除输入文件名的可能扩展名，并附加.ll、.s或.o作为扩展名。FileType选项在llvm/CodeGen/CommandFlags.inc头文件中定义，我们之前包含了它。这个选项不支持发出IR代码，所以我们添加了新的选项-emit-llvm，只有在与汇编文件一起使用时才会生效:\par

\begin{lstlisting}[caption={}]
	else {
		if (InputFilename.endswith(".mod"))
			OutputFilename = InputFilename.drop_back(4).str();
		else
			OutputFilename = InputFilename.str();
		switch (FileType) {
		case CGFT_AssemblyFile:
			OutputFilename.append(EmitLLVM ? ".ll" : ".s");
			break;
		case CGFT_ObjectFile:
			OutputFilename.append(".o");
			break;
		case CGFT_Null:
			OutputFilename.append(".null");
			break;
		}
	}
\end{lstlisting}

有些平台区分文本文件和二进制文件，所以必须在打开输出文件时提供正确的标志:\par

\begin{lstlisting}[caption={}]
	std::error_code EC;
	sys::fs::OpenFlags = sys::fs::OF_None;
	if (FileType == CGFT_AssemblyFile)
		OpenFlags |= sys::fs::OF_Text;
	auto Out = std::make_unique<llvm::ToolOutputFile>(
		OutputFilename, EC, OpenFlags);
	if (EC) {
		WithColor::error(errs(), Argv0) << EC.message() <<
			'\n';
		return false;
	}
\end{lstlisting}

现在我们可以将所需的Pass添加到PassManager中。TargetMachine类有一个实用程序方法，用于添加请求的类。因此，我们只需要检查用户是否请求输出LLVM IR代码:\par

\begin{lstlisting}[caption={}]
	legacy::PassManager PM;
	if (FileType == CGFT_AssemblyFile && EmitLLVM) {
		PM.add(createPrintModulePass(Out->os()));
	} else {
		if (TM->addPassesToEmitFile(PM, Out->os(), nullptr,
									FileType)) {
			WithColor::error() << "No support for file type\n";
			return false;
		}
	}
\end{lstlisting}

所有这些准备工作完成后，生成文件就可以归结为一个函数调用:\par

\begin{lstlisting}[caption={}]
	PM.run(*M);
\end{lstlisting}

如果不显式地保留该文件，那么ToolOutputFile类会自动删除该文件。这使得错误处理更容易，因为可能有很多地方需要处理错误，而在一切顺利的情况下只需要到达相应的期望。这里，我们成功地生成了代码，所以想要保留这个文件:\par

\begin{lstlisting}[caption={}]
	Out->keep();
\end{lstlisting}

最后，我们向调用者报告成功:\par

\begin{lstlisting}[caption={}]
	return true;
}
\end{lstlisting}

使用llvm::Module调用emit()方法，并调用CodeGenerator类，将按照请求生成代码。\par

假设在tinylang中有最大公约数算法存储在gcd.mod文件中。把它翻译成gcd.os目标文件，需要输入以下内容:\par

\begin{tcolorbox}[colback=white,colframe=black]
\$ tinylang –filetype=obj gcd.mod
\end{tcolorbox}

如果想在屏幕上直接检查生成的IR代码，可以输入以下代码:\par

\begin{tcolorbox}[colback=white,colframe=black]
\$ tinylang –filetype=asm –emit-llvm –o – gcd.mod
\end{tcolorbox}

让我们好好庆祝一下吧!至此，已经创建了一个完整的编译器，从读取源语言到生成汇编代码或目标文件。\par




